import NP from 'number-precision'
import { set } from 'lodash'
import dayjs from 'dayjs'
import type { AttendanceExcelItem, AttendanceItem, OrignExcelItemMap, OrignExcelMap, OverTimeExcelItem, OverTimeItem, WorkStatisticsMap } from '../types/attendance'
import { FULL_DAY_TIME, FULL_HALF_DAY_TIME, MD_TYPE } from '../enums/attendance'
import { calcWeekendWorkMD, filterAttendanceData, getWorkAllTime, isEffectStartWorkTime, isEffectWorkTime, makeEven } from '../utils/tools'
import type { Employee } from '@/module/informationManagement/types/employee'

/*
 * 将导入的excel数据格式进行转换。
 * @param list 考勤EXCEL列表、 加班EXCEL列表
 * @return 转换后的数据
*/
export function handAttendanceOriginalData(list: AttendanceExcelItem[]): OrignExcelMap<AttendanceExcelItem> {
  return list.reduce((acc, item) => {
    item.workDate = dayjs(item.workDate).format('YYYY/MM/DD')
    const attendanceDataCheck = filterAttendanceData(item)
    if (item.employeeName && item.workDate && attendanceDataCheck) {
      set(acc, `${item.employeeName}.${item.workDate}`, item)
    }

    return acc
  }, {} as OrignExcelMap<AttendanceExcelItem>)
}

export function handOTOriginalData(list: OverTimeExcelItem[]): OrignExcelMap<OverTimeExcelItem> {
  return list.reduce((acc, item) => {
    item.overTimeDate = dayjs(item.overTimeStartDate).format('YYYY/MM/DD')
    if (item.applicant && item.overTimeDate) {
      set(acc, `${item.applicant}.${item.overTimeDate}`, item)
    }

    return acc
  }, {} as OrignExcelMap<OverTimeExcelItem>)
}
// 获取当前员工的员工信息
function getEmployeeInfo(employeeData: Employee[], employeeName: string): Employee {
  return (employeeData.find(item => item.name === employeeName) || {}) as Employee
}
/**
 * @param attendanceOriginObj 考勤EXCEL
 * @param overTimeOriginObj 加班EXCEL
 * @return  统计总共的员工加班考勤数据
 */
export function handleOriginExcelData(attendanceOriginObj: OrignExcelMap<AttendanceExcelItem>, overTimeOriginObj: OrignExcelMap<OverTimeExcelItem>, employeeData: Employee[]) {
  const workStatisticsData: WorkStatisticsMap = {}

  for (const employeeName in attendanceOriginObj) {
    const employeeInfo = getEmployeeInfo(employeeData, employeeName)
    workStatisticsData[employeeName] = { attendanceList: [], overTimeList: [] }
    const attendanceList = workStatisticsData[employeeName].attendanceList
    const o = attendanceOriginObj[employeeName]
    const curEmpOtItem: OrignExcelItemMap<OverTimeExcelItem> | undefined = overTimeOriginObj?.[employeeName]
    for (const workDate in o) {
      const workDayItem = o[workDate]
      workDayItem.isWorkDay = workDayItem.workOrRest === '工作日'
      workDayItem.abnormalDuration = NP.plus(Number(workDayItem.vacationAmDuration || 0) + Number(workDayItem.vacationPmDuration || 0))

      const newDate = getAttendanceItem(workDayItem, employeeInfo, curEmpOtItem?.[workDate])
      if (Array.isArray(newDate)) {
        attendanceList.push(...newDate)
      }
    }

    // 统计每个人员工的加班时间汇总
    const otList = getOverTimeList(attendanceList, curEmpOtItem)
    workStatisticsData[employeeName].overTimeList = otList
    getTotalOverTimeDay(attendanceList, otList[0]?.totalDefaultOverTimeDay || 0)
    workStatisticsData[employeeName].attendanceList = attendanceList
  }
  return workStatisticsData
}

/**
 * 处理上传文件数据
 * @param attendanceOriginObj 考勤EXCEL列表
 * @param overTimeOriginObj 加班EXCEL列表
 * @return 员工列表、员工--考勤加班总计数据
 */
export function handleExcelData(attendanceOriginObj: OrignExcelMap<AttendanceExcelItem>, overTimeOriginObj: OrignExcelMap<OverTimeExcelItem>, employeeData: Employee[]) {
  // 员工列表
  const employeeList = getEmployeeList(attendanceOriginObj)
  // 处理加班时间 和 加班列表等信息。
  const workStatisticsData: WorkStatisticsMap = handleOriginExcelData(attendanceOriginObj, overTimeOriginObj, employeeData)
  return {
    employeeList,
    workStatisticsData,
  }
}
/**
 * 获取员工列表
 * @param list 考勤EXCEL列表
 * @return 员工列表
 */
function getEmployeeList(list: OrignExcelMap<AttendanceExcelItem>) {
  return Object.keys(list)
}

/**
 * 处理休假半天的考勤数据，拆分。
 * @param _attendanceItem 考勤EXCEL列表
 * @return 员工列表
 */
function handleVocationData(_attendanceItem: AttendanceItem, vacationDuration: number | string) {
  const _splitList: AttendanceItem[] = [
    {
      ..._attendanceItem,
      MD: MD_TYPE.N1,
      vacationTime: '',
      attendanceException: '',
    },
    {
      ..._attendanceItem,
      normalDuration: vacationDuration,
      defaultOverTimeMinute: '',
      voluntaryOverTimeHour: '',
      totalOverTimeDay: '',
      checkTime: '',
      remark: '',
      hasException: false,
      exceptionComment: '',
    },
  ]
  return _splitList
}
/*
  计算考勤时间是否正常
  * @param hasException: 是否存在考勤异常,
  * @param exceptionComment: 考勤异常说明,
  * @param isWorkStartPM?: 下午开始打卡上班
  * @param isHalfVocation?: 是否休假半天
*/
function checkNormalWorkTime(excelItem: AttendanceExcelItem): {
  hasException: boolean
  exceptionComment: string
  isWorkStartPM?: boolean
  isHalfVocation?: boolean
} {
  const { workDate, vacationType, vacationAmDuration, checkStartTime, checkEndTime, isWorkDay, abnormalDuration } = excelItem
  const result = {
    hasException: false,
    exceptionComment: '',
    isWorkStartPM: false,
    isHalfVocation: false,
  }
  // 调休整天，非工作日，不校验考勤打卡。
  if (!isWorkDay || (abnormalDuration === 1))
    return result

  /*
    1、是否正常考勤（无休假情况）
    2、考勤半天（上午考勤【打卡时间早于9:30】还是下午考勤【打卡时间早于2:00】）
    计算考勤时间【checkEndTime- Math.max(checkStartTime, 13:30) >= 4小时8分钟】 下午半天
    上午半天：【checkEndTime- Math.max(checkStartTime, 8:30) >= 4小时8分钟==> 248min】
    整体上班：【checkEndTime- Math.max(checkStartTime, 8:30) >= 9小时30分钟==> 570min】
  */

  // 休息半天，反之正常考勤一天
  const isHalfVocation = !!vacationType && (abnormalDuration !== 0 && abnormalDuration !== 1)

  // 上午请假。
  const isWorkStartPM = isHalfVocation && Number(vacationAmDuration) === 0.5

  // 上班是否迟到
  const isEffectiveCheck = isEffectStartWorkTime(workDate, checkStartTime, isWorkStartPM)
  if (!isEffectiveCheck) {
    result.exceptionComment = '迟到/早退'
    result.hasException = true
  }

  // 上班是否早退
  const isEffectiveWork = isEffectWorkTime(workDate, checkStartTime, checkEndTime, isHalfVocation, isWorkStartPM)
  if (!isEffectiveWork) {
    // 缺少打卡记录且不满足全勤，输入到日志中考勤异常。
    const lessWorkTime = (!isHalfVocation ? FULL_DAY_TIME : FULL_HALF_DAY_TIME) - getWorkAllTime(workDate, checkStartTime, checkEndTime, isWorkStartPM)
    result.exceptionComment = `${isHalfVocation ? '调休半天 ' : ''}${result.exceptionComment || ''} 考勤异常，上班时间缺少${lessWorkTime}分钟`

    result.hasException = true
  }

  return result
}

/*
  获取默认加班15分钟or8分钟
  * @param normalDuration: 工作日的休假时长,
  * @param md: 考勤日类型,
  * @return defaultOverTimeMinute: 默认加班分钟数
*/
export function getDefaultOverTimeMinute(normalDuration: number | string, md: string) {
  let defaultOverTimeMinute
  // 正常考勤打卡一天，或者存在请假半天的情况，计算默认加班时间。
  if (md === 'N1' || Number(normalDuration) < 1) {
    defaultOverTimeMinute = Math.ceil((Math.floor(Number(normalDuration) / 0.5)) * 7.5)
  }
  return {
    defaultOverTimeMinute,
  }
}

/*
  校验加班申请的时间是否存在异常情况。
  * @param item: 一条上班打卡的考勤数据,
  * @param isHalfVocation: 是否休假半天,
  * @param applyHours: 申请加班的时长,
  * @param isDirectlyOvertime: 是否是垂直落地加班,
  * @return boolean 校验加班申请的时间是否正确。
*/
export function checkTotalVoluntaryOTimeHourEffective(item: AttendanceExcelItem, isHalfVocation: boolean, isWorkStartPM: boolean, applyHours: string, isDirectlyOvertime: boolean): boolean {
  const { checkStartTime, checkEndTime, workDate, isWorkDay } = item
  if (!isWorkDay) {
    return calcWeekendWorkMD(workDate, checkStartTime, checkEndTime) >= Number(applyHours)
  }
  // 出勤有效时间-分钟数
  const duringMinsTime = getWorkAllTime(workDate, checkStartTime, checkEndTime, isWorkStartPM)
  const applyMinsTimes = NP.times(applyHours, 480) // 申请的加班时间--转成分钟数。
  // 是否垂直加班
  const extraTime = isDirectlyOvertime ? 0 : 60
  // 需要在岗的时间
  const totalRequiredTime = (isHalfVocation ? FULL_HALF_DAY_TIME : FULL_DAY_TIME) + applyMinsTimes + extraTime

  return duringMinsTime >= totalRequiredTime
}

/*
    获取考勤每一项数据
  * @param excelItem: 一条上班打卡的考勤数据,
  * @param employeeInfo: 员工信息,
  * @param otItem: 加班数据,
  * @return AttendanceItem[] 计算后的考勤信息
*/
function getAttendanceItem(excelItem: AttendanceExcelItem, employeeInfo: Employee, otItem?: OverTimeExcelItem) {
  // 休息日 没申请加班记录 删除这条数据
  if (!excelItem.isWorkDay && !otItem) {
    return null
  }

  // 考勤是否正常
  const { hasException, exceptionComment, isHalfVocation, isWorkStartPM } = checkNormalWorkTime(excelItem)

  // 休假时长\正常时长
  const { vacationDuration, normalDuration } = getDuration(excelItem)
  // MD类型
  const { MD } = getMD(excelItem)

  // 打卡时间、休假时间
  const { checkTime, vacationTime } = getWorkTime(excelItem)
  // 默认加班15分钟 or 8 分钟
  const { defaultOverTimeMinute } = getDefaultOverTimeMinute(normalDuration, MD)

  // 星期
  const weekday = dayjs(excelItem.workDate).format('dddd')

  // 考勤列表每一项
  const _attendanceItem: AttendanceItem = {
    ...employeeInfo,
    employeeName: excelItem.employeeName,
    workDate: excelItem.workDate,
    weekday,
    remark: excelItem.remark,
    MD,
    normalDuration,
    checkTime,
    vacationTime,
    defaultOverTimeMinute,
    attendanceException: excelItem.vacationType,
    hasException,
    exceptionComment,
  }

  // 是否有主动加班时长
  if (otItem) {
    // 是否直接加班
    const isDirectlyOvertime = otItem?.isDirectlyOvertime !== '是'
    // 校验加班时长是否存在异常
    const isEffectOTHours = checkTotalVoluntaryOTimeHourEffective(excelItem, !!isHalfVocation, !!isWorkStartPM, otItem.finalOvertimeDay, isDirectlyOvertime)
    if (!isEffectOTHours) {
      _attendanceItem.hasException = true
      _attendanceItem.exceptionComment = `${_attendanceItem.exceptionComment || ''} 加班时长与实际下班打卡时间不符`
    }
    // otItem.finalOverTimeHour
    _attendanceItem.voluntaryOverTimeHour = NP.times(otItem.finalOvertimeDay, 8)
    _attendanceItem.totalOverTimeDay = otItem.finalOvertimeDay
  }
  // 是否需要拆分数据
  // 工作日、补班日且休假时长大于0小于1需要拆分数据
  if (excelItem.isWorkDay && (vacationDuration !== 0 && vacationDuration !== 1)) {
    return handleVocationData(_attendanceItem, vacationDuration)
  }
  return [_attendanceItem]
}

// 获取MD
function getMD(excelItem: AttendanceExcelItem) {
  // 默认正常
  const MD = MD_TYPE[excelItem.vacationType as keyof typeof MD_TYPE] || MD_TYPE.N1
  return {
    MD,
  }
}

// 获取正常时长、休假时长
function getDuration(excelItem: AttendanceExcelItem) {
  // 正常时长
  let normalDuration: number | string = 0
  // 请假时长
  let vacationDuration: number | string = 0

  const {
    isWorkDay,
    vacationType,
    abnormalDuration, // 请假时长
  } = excelItem
  if (!isWorkDay)
    return { normalDuration, vacationDuration }
  // 考勤正常（包含入职、补卡）
  const isWorkNormal = !vacationType || ['入职', '补卡'].includes(vacationType)

  // 考勤正常、不休假、休假一天都为1
  if (isWorkNormal || abnormalDuration === 0 || abnormalDuration === 1) {
    normalDuration = 1
    vacationDuration = 0
  }
  else {
    normalDuration = NP.minus(1, Number(abnormalDuration))
    vacationDuration = Number(abnormalDuration)
  }

  return {
    normalDuration,
    vacationDuration,
  }
}

// 获取打卡时间、休假时间
function getWorkTime(excelItem: AttendanceExcelItem) {
  let checkTime = ''
  let vacationTime = ''

  const {
    checkStartTime,
    checkEndTime,
    vacationStartTime,
    vacationEndTime,
  } = excelItem

  // 打卡时间: 不休假且有打卡时间
  if (checkStartTime && checkEndTime) {
    checkTime = `${checkStartTime} - ${checkEndTime}`
  }

  // 休假时间: 有休假时间
  if (vacationStartTime && vacationEndTime) {
    vacationTime = `${vacationStartTime} - ${vacationEndTime}`
  }

  return {
    checkTime,
    vacationTime,
  }
}

/*
  将多条加班数据转成一个，并且汇总主动加班时间的时间。
  * @param list: 某个员工的转换后的考勤数据列表
  * @param curEmpOtItem: 某个员工的所有加班数据,
  * @return OverTimeItem[] 统计某个员工的加班数据
*/
function getOverTimeList(list: AttendanceItem[], curEmpOtItem: OrignExcelItemMap<OverTimeExcelItem> | undefined) {
  if (list.length === 0)
    return []
  const overTimeItem: OverTimeItem = {
    employeeName: list?.[0]?.employeeName,
    totalVoluntaryOverTimeHour: 0,
    totalVoluntaryOverTimeDay: 0,
    totalDefaultOverTimeMinute: 0,
    totalDefaultOverTimeDay: 0,
    totalOverTimeDay: 0,
  }

  list.forEach((item: AttendanceItem) => {
    const { defaultOverTimeMinute, workDate } = item
    // 默认加班时长
    overTimeItem.totalDefaultOverTimeMinute = NP.plus(overTimeItem.totalDefaultOverTimeMinute, defaultOverTimeMinute || 0)
    // 当前记录存在主动加班时长
    const voluntaryOverTimeHour = curEmpOtItem?.[workDate]?.finalOverTimeHour
    if (voluntaryOverTimeHour) {
      // 主动加班时长（小时，按月累计）
      overTimeItem.totalVoluntaryOverTimeHour = NP.plus(overTimeItem.totalVoluntaryOverTimeHour, voluntaryOverTimeHour)
    }
  })
  // 默认加班工时（分钟，按月累计）
  overTimeItem.totalDefaultOverTimeDay = NP.divide(makeEven(Number(overTimeItem.totalDefaultOverTimeMinute)), 480)
  // 主动加班时长（天，按月累计）
  overTimeItem.totalVoluntaryOverTimeDay = NP.divide(overTimeItem.totalVoluntaryOverTimeHour, 8)
  // 累计加班（天，按月累计）
  overTimeItem.totalOverTimeDay = NP.plus(overTimeItem.totalDefaultOverTimeDay, overTimeItem.totalVoluntaryOverTimeDay)

  return [overTimeItem]
}

// 修改默认加班总时长
function getTotalOverTimeDay(attendanceList: AttendanceItem[], totalDefaultOverTimeDay: number | string) {
  const _employeeList = new Set<string>()

  // 遍历考勤列表
  attendanceList.sort((a, b) => new Date(a.workDate).getTime() - new Date(b.workDate).getTime()).forEach((attendanceItem: AttendanceItem) => {
    const { employeeName, MD, voluntaryOverTimeHour } = attendanceItem
    const canWriteDefaultOTTime = attendanceItem.totalOverTimeDay === undefined || attendanceList.length === 1
    // 第一条为N1的数据
    if (!_employeeList.has(employeeName) && MD === MD_TYPE.N1 && canWriteDefaultOTTime) {
      // 当条数据主动加班时长小时转为天
      const voluntaryOverTimeDay = NP.divide(voluntaryOverTimeHour || 0, 8)
      // 本月默认加班总时长（天） + 当条数据主动加班时长（天）
      attendanceItem.totalOverTimeDay = NP.plus(totalDefaultOverTimeDay, voluntaryOverTimeDay)
      attendanceItem.remark = attendanceItem.remark ? `${attendanceItem.remark};15分钟转换KPI` : '15分钟转换KPI'
      // 只执行一次
      _employeeList.add(employeeName)
    }
  })
}
